import json
import math
import os
import random
import shutil
import string

from django.http.response import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

import tma.utils.settings as tma_setting
from tma.utils.tools import (
    cal_line_angle,
    get_angle_between_lines,
    get_line_slope_and_intercept,
)
from tma.utils.prediction import (
    TMA,
    Target,
)

from .tma_track_demon import cal_target_data, generate_submarines

if os.path.exists(tma_setting.TMA_tmp_dir):
    shutil.rmtree(tma_setting.TMA_tmp_dir)
os.mkdir(tma_setting.TMA_tmp_dir)


def tmaDemoView(request):
    return render(request, "tma_demo.html", locals())


def tmaView(request):
    return render(request, "tma.html", locals())


def foundationView(request):
    return render(request, "foundation.html", locals())


def broadbandView(request):
    return render(request, "broadband.html", locals())


def uuvSetupView(request):
    return render(request, "uuv-setup.html", locals())


def installationView(request):
    return render(request, "RA-install.html", locals())


def torpedoSetupView(request):
    return render(request, "torpeto.html", locals())


def buoyView(request):
    return render(request, "buoy.html", locals())


def multiPlayersView(request):
    return render(request, "multi_players.html", locals())


def subrocView(request):
    return render(request, "subroc.html", locals())


def shkvalView(request):
    return render(request, "shkval.html", locals())


def cmView(request):
    return render(request, "counter_measure.html", locals())


def decoyView(request):
    return render(request, "mobile_decoy.html", locals())


def spdNoiseView(request):
    return render(request, "spd_noise.html", locals())


# !!! test view
def testView(request):
    return render(request, "test.html", locals())


def t2View(request):
    return render(request, "t2.html", locals())


def tmaCaculateView(request):
    return render(request, "tma_caculate.html", locals())


# !!! TMA demo
class TmaTrackData(APIView):
    def get(self, request):
        recv_data = request.GET
        try:
            duration = int(recv_data["duration"])
            assert 3 < duration < 90
            observe_interval = int(recv_data["observe_interval"])
            assert 1 <= observe_interval < 11
            target_speed = int(recv_data["target_speed"])
            assert 0 <= target_speed <= 65
            target_course = int(recv_data["target_course"])
            assert 0 <= target_course < 360
            own_speed = int(recv_data["own_speed"])
            assert 0 <= own_speed < 60
            own_course = int(recv_data["own_course"])
            assert 0 <= own_course < 360
            target_init_distance = int(recv_data["target_init_distance"])
            assert 1 <= target_init_distance < 51
            target_init_bearing = int(recv_data["target_init_bearing"])
            assert 0 <= target_init_bearing < 360
        except AssertionError:
            return Response(
                {"error": "check you input data!"},
                status=status.HTTP_406_NOT_ACCEPTABLE,
            )
        return Response(
            cal_target_data(
                *(
                    generate_submarines(
                        (own_course, own_speed),
                        (target_course, target_speed),
                        target_init_distance,
                        target_init_bearing,
                    )
                ),
                duration,
                observe_interval,
            )
        )


# !!! TMA prediction
TMA_STORE = {}


@csrf_exempt
def tmaImageProcess(request):
    # myFile =request.FILES.get("myfile", None)    # 获取上传的文件，如果没有文件，则默认为None
    files = request.FILES.getlist("files")
    file = files[0]
    # logger.info(f"name: {fit.name} size: {round(fit.size/ 1024 / 1024 / 1024, 5)} G")

    name_prefix = "".join(random.choices(string.ascii_lowercase + string.digits, k=20)) + "_"
    image_file = os.path.join(tma_setting.TMA_tmp_dir, name_prefix + file.name)
    with open(image_file, mode="wb") as f:
        for chunk in file.chunks():
            f.write(chunk)

    tma = TMA(Target(), image_file)

    if tma_setting.Delete_image_file and os.path.exists(image_file):
        os.remove(image_file)

    tma.lines_original = tma.lines
    tma.target.pd_with_track.track_distance_original = tma.target.pd_with_track.track_distance
    if len(TMA_STORE) > 50:
        first_key = next(iter(TMA_STORE))
        TMA_STORE.pop(first_key)
    TMA_STORE[image_file] = tma
    print("-" * 44)
    print(tma.lines)
    print(tma.target.pd_with_track.track_distance)
    return JsonResponse({
        "code": 0,
        "lines": tma.lines,
        "canvas_size": tma.canvas_size,
        "image_size": tma.image_size,
        "tma_instance_key": image_file,
        "bl_time_tick": tma.bl_time_tick,
    })


@csrf_exempt
def tmaPredict(request):
    if request.method == "POST":
        req_data = json.loads(request.body)
        try:
            tma_instance_key = req_data.get("tma_instance_key")
            tma = TMA_STORE[tma_instance_key]
            tma.lines = req_data.get("new_tma_lines")
            # !!@ ensure line slope of self.lines[0] > [-1]
            if abs(get_line_slope_and_intercept(tma.lines[0])[0]) > abs(get_line_slope_and_intercept(tma.lines[-1])[0]):
                tma.lines.reverse()
            tma.bl_time_tick = req_data.get("new_bl_time_tick")
            tma.update_bl_related_info()
            own_sub_spd = int(req_data.get("own_sub_spd"))
            own_sub_course = int(req_data.get("own_sub_course"))
            target_sub_spd = int(req_data.get("target_sub_spd"))
            assert len(tma.lines) == len(tma.bl_time_tick)
        except KeyError:
            return JsonResponse({
                "code": 2,
                "message": f"Unknown key {tma_instance_key}",
            })

        if tma.target.pd_with_track.track_detected:
            if len(tma.lines) < len(tma.lines_original):
                tma.target.pd_with_track.track_distance = (
                    tma.target.pd_with_track.track_distance_original * len(tma.lines) / len(tma.lines_original)
                )
            else:
                tma.target.pd_with_track.track_distance = tma.target.pd_with_track.track_distance_original
                tma.lines = tma.lines_original

        # print('-' * 88)
        # print(TMA_STORE)
        # print(tma.lines)
        # print(new_tma_lines)
        # print(tma.image_size)
        # print('=' * 88)

        # !!@ prediction
        # tma.tuplize()
        # tma.predict_BFGS()
        #
        # tma.predict_with_target_track()
        tma.predict_with_target_track_new()  # minimize() to find smallest_error
        # tma.predict_with_target_track_new_05() # bl angle-diff metric
        #
        # tma.predict_without_target_track_new_02()  # two stage strategy to optimize
        tma.predict_without_target_track_new_04()  # minimize() to find smallest_error
        # tma.predict_without_target_track_new_05()  # bl angle-diff metric
        # tma.predict_without_target_track_new_06()  # minimize() to find best track distance
        # tma.predict_without_target_track_full_brute_force()
        #
        tma.single_leg_ekelund_ranging()
        #

        # !!@ debug output
        # if tma.target.pd_with_track.track_detected:
        #     print("-------------------------------------------- pd_with_track ---")
        #     print(f"number of bearing line: {len(tma.lines)}")
        #     print(f"target track: {tma.target.pd_with_track.track}")
        #     print(f"target track smallest_fitness_error: {tma.target.pd_with_track.track_error}")
        #     print(f"target track travel_distance: {tma.target.pd_with_track.track_distance}")
        #     print(f"target track heading: {tma.target.pd_with_track.track_heading}")
        #     print(tma.target.pd_with_track.top3_track)
        # print("--------------------------------------------- pd_without_track ---")
        # print(f"number of bearing line: {len(tma.lines)}")
        # print(f"target track: {tma.target.pd_without_track.track}")
        # print(f"target track smallest_fitness_error: {tma.target.pd_without_track.track_error}")
        # print(f"target track travel_distance: {tma.target.pd_without_track.track_distance}")
        # print(f"target track heading: {tma.target.pd_without_track.track_heading}")
        # print(tma.target.pd_without_track.top3_track)
        # print("")
        # print("")

        # !!@ basic return value
        ret = {
            "code": 0,
            "lines": tma.lines,
            "canvas_size": tma.canvas_size,
            "track_detected": tma.target.pd_with_track.track_detected,
        }

        # !!@ add track detail
        ret["pd_track_ratio"] = 0
        if tma.target.pd_with_track.track_detected:
            ret["pd_with_track"] = {
                "track_error": tma.target.pd_with_track.track_error,
                "track_distance": tma.target.pd_with_track.track_distance,
                "track_heading": [round(i, 1) for i in tma.target.pd_with_track.track_heading],
                "track": tma.target.pd_with_track.track,
            }
            ret["pd_track_ratio"] = round(
                tma.target.pd_without_track.track_distance / tma.target.pd_with_track.track_distance,
                2,
            )

        ret["pd_without_track"] = {
            "track_error": tma.target.pd_without_track.track_error,
            "track_distance": tma.target.pd_without_track.track_distance,
            "track_heading": [round(i, 1) for i in tma.target.pd_without_track.track_heading],
            "track": tma.target.pd_without_track.track,
        }

        # !!@ add ekelund_range detail
        ret["ekelund_range"] = tma.target.ekelund_range
        ret["ekelund_range_spd_top"] = tma_setting.Ekelund_range_spd_top
        ret["ekelund_range_spd_btm"] = tma_setting.Ekelund_range_spd_btm

        # !!@ ekelund_range_extra_info
        ekelund_range_enhanced_without_track = []
        ekelund_range_enhanced_with_track = []
        own_cross_spd_list = []
        if own_sub_spd > 0 and own_sub_course >= 0 and target_sub_spd > 0:
            for idx, line in enumerate(tma.lines):
                br_ratio = tma.bl_change_ratio[idx]
                heading01, heading02 = cal_line_angle(line)
                own_cross_spd = own_sub_spd * math.sin(
                    math.radians(get_angle_between_lines(heading01, heading02, own_sub_course))
                )
                assert own_cross_spd > 0
                own_cross_spd_list.append(round(own_cross_spd, 1))

                target_sub_course = max(tma.target.pd_without_track.track_heading)
                target_cross_spd = target_sub_spd * math.sin(
                    math.radians(get_angle_between_lines(heading01, heading02, target_sub_course))
                )
                assert target_cross_spd > 0
                if ret["pd_track_ratio"] > 0:
                    total_bl_cross_spd = own_cross_spd * ret["pd_track_ratio"] + target_cross_spd
                else:
                    total_bl_cross_spd = own_cross_spd + target_cross_spd
                ekelund_range = abs(round(total_bl_cross_spd / br_ratio, 1))
                ekelund_range_enhanced_without_track.append(ekelund_range)

                if tma.target.pd_with_track.track_detected:
                    target_sub_course = max(tma.target.pd_with_track.track_heading)
                    target_cross_spd = target_sub_spd * math.sin(
                        math.radians(get_angle_between_lines(heading01, heading02, target_sub_course))
                    )
                    assert target_cross_spd > 0
                    total_bl_cross_spd = own_cross_spd + target_cross_spd
                    ekelund_range = abs(round(total_bl_cross_spd / br_ratio, 1))
                    ekelund_range_enhanced_with_track.append(ekelund_range)
                else:
                    ekelund_range_enhanced_with_track.append(0)
            ret["ekelund_range_enhanced"] = {
                "without_track": ekelund_range_enhanced_without_track,
                "with_track": ekelund_range_enhanced_with_track,
                "track_detected": tma.target.pd_with_track.track_detected,
            }
            ret["own_cross_spd_list"] = own_cross_spd_list
        else:
            ret["ekelund_range_enhanced"] = None
            ret["own_cross_spd_list"] = None
        # print('8' * 88)
        # print(ret['ekelund_range_enhanced'])

        # !!@ image size info
        max_x = max(tma.target.pd_without_track.track[0], tma.target.pd_without_track.track[2])
        max_y = max(tma.target.pd_without_track.track[1], tma.target.pd_without_track.track[3])
        min_x = min(tma.target.pd_without_track.track[0], tma.target.pd_without_track.track[2])
        min_y = min(tma.target.pd_without_track.track[1], tma.target.pd_without_track.track[3])

        if tma.target.pd_with_track.track_detected:
            max_x = max(
                max_x,
                tma.target.pd_with_track.track[0],
                tma.target.pd_with_track.track[2],
            )
            max_y = max(
                max_y,
                tma.target.pd_with_track.track[1],
                tma.target.pd_with_track.track[3],
            )
            min_x = min(
                min_x,
                tma.target.pd_with_track.track[0],
                tma.target.pd_with_track.track[2],
            )
            min_y = min(
                min_y,
                tma.target.pd_with_track.track[1],
                tma.target.pd_with_track.track[3],
            )
        for line in tma.lines:
            max_x = max(line[0], line[2], max_x)
            max_y = max(line[1], line[3], max_y)
            min_x = min(line[0], line[2], min_x)
            min_y = min(line[1], line[3], min_y)

        ret["image_size"] = [
            math.floor(min_x),
            math.floor(min_y),
            math.ceil(max_x),
            math.ceil(max_y),
        ]
        return JsonResponse(ret)

